package jf.jextn;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import jf.jextn.util.ImmutableSimpleEntry;
import jf.jextn.util.Pair;

import org.slf4j.helpers.MessageFormatter;

/**
 * @author Sandeep Kadyan
 */
public class JExtn {

    public static boolean isNullOrBlank(String s) {
        return s == null || s.length() == 0;
    }

    public static boolean hasValue(String text) {
        return !isNullOrBlank(text);
    }

    // /**
    // * @param prop
    // * @return
    // */
    // @SuppressWarnings("unchecked")
    // public static <K, V> Map<K, V> cast(Map<?, ?> map) {
    // return (Map<K, V>) map;
    // }
    //
    // /**
    // * @param prop
    // * @return
    // */
    // @SuppressWarnings("unchecked")
    // public static <V> Set<V> cast(Set<?> set) {
    // return (Set<V>) set;
    // }
    //
    // /**
    // * @param list
    // * @return
    // */
    // @SuppressWarnings("unchecked")
    // public static <V> List<V> cast(List<?> list) {
    // return (List<V>) list;
    // }
    //
    // /**
    // * @param prop
    // * @return
    // */
    // @SuppressWarnings("unchecked")
    // public static <V> Collection<V> cast(Collection<?> collection) {
    // return (Collection<V>) collection;
    // }
    //
    // /**
    // * @param o
    // * @return
    // */
    // @SuppressWarnings("unchecked")
    // public static <V> V cast(Object o) {
    // return (V) o;
    // }

    /**
     * @param number
     *            the number to be search in numbers.
     * @param numbers
     *            the numbers
     * @return true if numbers has element same as number.
     */
    public static boolean in(int number, int[] numbers) {
        if (numbers == null || numbers.length == 0) {
            return false;
        }

        int len = numbers.length / 2 + 1;
        for (int i = 0; i < len;) {
            if (number == numbers[i]) {
                return true;
            }

            i++;

            if (number == numbers[numbers.length - i]) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param value
     *            the value to be search in values.
     * @param values
     *            the values
     * @return true if values has any element same as value.
     */
    public static boolean in(Object value, Object[] values) {
        if (isEmpty(values)) {
            return false;
        }

        int len = values.length / 2 + 1;
        for (int i = 0; i < len;) {
            if (equals(value, values[i])) {
                return true;
            }

            i++;

            if (equals(value, values[values.length - i])) {
                return true;
            }
        }

        return false;
    }

    /**
     * If the candidate is not in the limit defined by <code>lower</code> and
     * <code>upper</code>, then this method will return the closed maximum limit
     * to candidate.
     * 
     * @param candidate
     *            the candidate
     * @param lower
     *            the lower limit
     * @param upper
     *            the upper limit
     * @return the nearest maximum limit if candidate is not fit in limit
     *         defined by lower and upper.
     */
    public static int getNearestMaximum(int candidate, int lower, int upper) {
        if (lower > upper) {
            throw new IllegalArgumentException("Lower must be less than upper");
        }

        if (candidate < lower) {
            candidate = lower;
        } else if (candidate > upper) {
            candidate = upper;
        }

        return candidate;
    }

    /**
     * If the target is null it will throws the {@link IllegalArgumentException}
     * 
     * @param o
     *            the target
     * @param msg
     *            the message used as exception description
     * @throws IllegalArgumentException
     */
    public static void checkNull(Object o, String msg) {
        if (o == null) {
            throw new IllegalArgumentException(msg);
        }
    }

    /**
     * If the target is null it will throws the {@link IllegalArgumentException}
     * 
     * @param o
     *            the target
     * @param msg
     *            the message format used as exception description
     * @param args
     *            the arguments used to build the exception message
     * 
     * @throws IllegalArgumentException
     */
    public static void checkNull(Object o, String msg, Object... args) {
        if (o == null) {
            throw new IllegalArgumentException(String.format(msg, args));
        }
    }

    /**
     * @param <T>
     *            inferred Type of value
     * @param target
     *            target instance.
     * @param val
     *            defaulted value
     * @return target if not null otherwise return {@code val}.
     */
    public static final <T> T ifNull(T target, T val) {
        return target != null ? target : val;
    }

    /**
     * @param target
     *            target instance.
     * @param val
     *            defaulted value
     * @return target if not null or not Blank otherwise return {@code val}.
     */
    public static final String ifNullOrBlank(String target, String val) {
        return isNullOrBlank(target) ? val : target;
    }

    /**
     * Create a new pair for passed two object.
     * 
     * @param <K>
     *            type of first member of pair.
     * @param <V>
     *            type of second member of pair.
     * @param first
     *            member
     * @param second
     *            member
     * @return an immutable pair instance.
     */
    public static <K, V> Pair<K, V> newPair(K first, V second) {
        return new ImmutableSimpleEntry<K, V>(first, second);
    }

    /**
     * Parse and create the URL instance for passed url.
     * 
     * @param url
     *            the absolute URL
     * @return {@link java.net.URL} instance or null if passed url cannot be
     *         parsed.
     */
    public static URL toURL(String url) {
        try {
            return new URL(url);
        } catch (MalformedURLException e) {
            return null;
        }
    }

    /**
     * Check if the passed collection is null or has zero elements.
     * 
     * @param collection
     *            the collection
     * @return true if the passed collection is null or has zero elements.
     */
    public static boolean isEmpty(Collection<?> collection) {
        return collection == null || collection.isEmpty();
    }

    /**
     * Check if the passed array is null or has zero elements.
     * 
     * @param array
     *            the array
     * @return true if the passed array is null or has zero elements.
     */
    public static boolean isEmpty(Object[] array) {
        return array == null || array.length == 0;
    }

    /**
     * Compare two objects for their equality. It returns true if both instances
     * are null or equals.
     * 
     * @param o1
     *            first instance
     * @param o2
     *            second instance
     * @return true if both object are equals. Two null instances are also
     *         equals.
     */
    public static boolean equals(Object o1, Object o2) {
        return o1 == o2 ? true : (o1 == null ? o2 == null : (o2 == null ? false : o1.equals(o2)));
    }

    public static int hashCode(Object o) {
        return o == null ? 0 : o.hashCode();
    }

    public static <V extends Comparable<? super V>> int compare(V o1, V o2) {
        return o1 == o2 ? 0 : (o1 == null ? -1 : o2 == null ? 1 : o1.compareTo(o2));
    }

    /**
     * Compares the passed collection of {@code Comparable<T>}.
     * 
     * @param <T>
     *            type of the elements of the collections
     * @param o1
     *            first collection.
     * @param o2
     *            second collection.
     * @return -1, 0 or 1
     */
    public static <T extends Comparable<? super T>> int compare(Collection<T> o1, Collection<T> o2) {
        if (o1 == o2) {
            return 0;
        }

        int cmp = 0;
        Iterator<T> i1 = o1.iterator(), i2 = o2.iterator();

        while (i1.hasNext() && i2.hasNext()) {
            cmp = i1.next().compareTo(i2.next());
            if (cmp != 0) {
                break;
            }
        }
        if (cmp == 0) {
            if (i1.hasNext()) {
                cmp = -1;
            }
            if (i2.hasNext()) {
                cmp = 1;
            }
        }

        return cmp;
    }

    /**
     * @param properties
     *            the properties
     * @param namePrefix
     *            the property name prefix
     * @param stripPrefix
     *            true if prefix needs to be removed.
     * @return the subset of the properties.
     */
    public static Properties subsetOf(Properties properties, String namePrefix, boolean stripPrefix) {
        Properties subset = new Properties();
        for (Map.Entry<Object, Object> e : properties.entrySet()) {
            String prop = String.valueOf(e.getKey());
            String val = String.valueOf(e.getValue());
            if (prop.startsWith(namePrefix)) {
                if (stripPrefix) {
                    prop = prop.substring(namePrefix.length());
                }

                subset.setProperty(prop, val);
            }
        }

        return subset;
    }

    public static Properties subsetOf(Properties properties, String namePrefix) {
        return subsetOf(properties, namePrefix, true);
    }

    /**
     * WARNING! This method does not handle the escaping of the values.
     * 
     * @param value
     * @param trimValues
     * @param ignoreBlank
     * @return
     */
    public static List<String> readCSVLine(String value, boolean trimValues, boolean ignoreBlank) {
        List<String> list = new ArrayList<String>();
        if (!isNullOrBlank(value)) {
            String[] values = value.split("\\,");
            for (String val : values) {
                if (trimValues) {
                    val = val.trim();
                }

                if (!(ignoreBlank && val.isEmpty())) {
                    list.add(val);
                }
            }
        }

        return list;
    }

    /**
     * Set the bit specified by <code>which</code> argument in the long value
     * and return the new value.
     * 
     * @param value
     *            the value
     * @param which
     *            bit position (start from Left Hand)
     * @return new value
     */
    public static long setFlag(long value, int which) {
        checkValidLongIndex(which);
        value |= (1L << which);

        return value;
    }

    /**
     * Clear the bit specified by <code>which</code> argument in the long value
     * and return the new value.
     * 
     * @param value
     *            the value
     * @param which
     *            bit position (start from Left Hand)
     * @return new value
     */
    public static long clearFlag(long value, int which) {
        checkValidLongIndex(which);
        value &= ~(1L << which);

        return value;
    }

    /**
     * Determine if the bit specified by <code>which</code> argument in the long
     * value is set or not.
     * 
     * @param value
     *            the value
     * @param which
     *            bit position (start from Left Hand)
     * @return true if bit is set.
     */
    public static boolean isFlagSet(long value, int which) {
        checkValidLongIndex(which);

        return (value & (1L << which)) != 0;
    }

    private static void checkValidLongIndex(int which) {
        if (which < 0 || which >= Long.SIZE) {
            throw new IllegalArgumentException("invalid flag index :" + which);
        }
    }

    public static void checkCondition(boolean condition, String msg) {
        if (condition == false) {
            throw new IllegalArgumentException(msg);
        }

    }

    /**
     * @param pattern
     *            the SLF4J message pattern
     * @param args
     * @return
     */
    public static IllegalArgumentException throwIAE(String pattern, Object... args) {
        String msg = MessageFormatter.arrayFormat(pattern, args).getMessage();

        return new IllegalArgumentException(msg);
    }

}
